package cn.migu.music.hangupsms.dbcache;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.base.Splitter;
import com.google.common.base.Stopwatch;

import cn.migu.music.common.redis.IRedisClient;
import cn.migu.music.common.utils.LogUtils;
import cn.migu.music.hangupsms.common.config.HangUpSmsConfig;
import cn.migu.music.hangupsms.common.entity.BlackUserInfo;
import cn.migu.music.hangupsms.common.entity.VrbtSendMsg;
import cn.migu.music.hangupsms.common.util.DidUtils;
import cn.migu.music.hangupsms.remote.SMSFeignService;
import cn.migu.music.hangupsms.repository.BlackUserInfoDbMapper;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class BlackUserCacheManager {
    private static final String            BLACK_LIST_TOTAL  = "cljz.hupsms.blkusr.total";
    private static final String            BLACK_LIST_KEY    = "cljz.hupsms.blkusr.";
    private static final String            SECOND_FORMAT     = "yyyyMMddHHmmss";
    private static final String            LOAD_DB_INTERRUPT = "cljz.hupsms.blkusr.loaddata.interrupt";
    private static final String            LOAD_DB_LOCK      = "cljz.hupsms.blkusr.loaddata.lock";
    private static final String            LOAD_DB_FLAG      = "cljz.hupsms.blkusr.loaddata.flag";
    private static final DateTimeFormatter DFT               = DateTimeFormatter.ofPattern(SECOND_FORMAT);
    private static final Splitter          SPLITTER          = Splitter.on("|");
    @Autowired
    private IRedisClient                   redisClient;
    @Autowired
    private HangUpSmsConfig                hangUpSmsConfig;
    @Autowired
    private BlackUserInfoDbMapper          blackUserInfoMapper;
    @Autowired
    private SMSFeignService                smsFeignService;
    private final ExecutorService                executor          = Executors.newSingleThreadExecutor();

    @PostConstruct
    public void init() {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                loadAllBlackList(true);
            }
        });
    }

    @SuppressWarnings("all")
    public void loadAllBlackList(boolean startUp) {
        if (!redisClient.setnxExpire(LOAD_DB_LOCK, "1", 3600)) {
            LogUtils.info(log, "load black user to redis locked");
            return;
        }
        String interrupt = redisClient.get(LOAD_DB_INTERRUPT);
        if (StringUtils.equals("1", interrupt)) {
            redisClient.del(LOAD_DB_INTERRUPT);
            return;
        }
        if (startUp) {
            String done = redisClient.get(LOAD_DB_FLAG);
            if (StringUtils.equals(done, "done")) {
                return;
            }
        }
        Stopwatch stopwatch = Stopwatch.createStarted();
        LogUtils.info(log, "load black user to redis start!");
        int retryTimes = 0;
        // 失败后每隔5分钟重试一次
        while (true) {
            try {
                int sum = doFillUpRedisData();
                LogUtils.info(log, "load black user finish! cost:" + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms, size:" + sum);
                redisClient.del(LOAD_DB_LOCK);
                redisClient.del(LOAD_DB_INTERRUPT);
                redisClient.set(BLACK_LIST_TOTAL, String.valueOf(sum));
                break;
            } catch (Exception e) {
                LogUtils.error(e, log, "load black user error. retry:%d", retryTimes);
            }
            retryTimes += 1;
            if (retryTimes >= hangUpSmsConfig.getBlackUserCacheRetry()) {
                sendWarningSms("挂机短信黑名单Redis加载任务执行失败(已重试" + retryTimes + "次)！请检查日志后重新执行任务：hangupSmsBlackUserRedisRefreshJob");
                break;
            }
            try {
                Thread.sleep(1000 * 60 * 5);
            } catch (InterruptedException e) {
                LogUtils.warn(e, log, "InterruptedException");
                break;
            }
        }
    }

    private void sendWarningSms(String content) {
        List<String> phoneList = SPLITTER.splitToList(hangUpSmsConfig.getWarningPhones());
        VrbtSendMsg event = new VrbtSendMsg();
        event.setMsgPort(hangUpSmsConfig.getMsgSendPort());
        event.setMsgContent(content);
        event.setAccessPlatformID("014000E");
        event.setDid(hangUpSmsConfig.getSendDid());
        event.setSeq(DidUtils.generateSeq(hangUpSmsConfig.getSendDid()));
        event.setDidpwd(DidUtils.generateMD5String(event.getSeq() + hangUpSmsConfig.getSendDidPwd()));
        event.setMsgtemplateId("hangup_sms_service_warning");
        for (String phone : phoneList) {
            try {//123123
                event.setMsisdn(phone);
                smsFeignService.vRBTSendMsgV3(event);
            } catch (Exception e) {
                LogUtils.error(e, log, "sms send failed");
            }
        }
    }

    private int doFillUpRedisData() {
        int sum = 0;
        boolean done = true;
        int pageSize = hangUpSmsConfig.getBlackUserLoadSize();
        String phoneNumber = "0";
        List<BlackUserInfo> blackUserInfoList = blackUserInfoMapper.selectByAllByPhoneNumber(phoneNumber, pageSize);
        while (CollectionUtils.isNotEmpty(blackUserInfoList)) {
            if (hangUpSmsConfig.isNeedLog()) {
                LogUtils.info(log, "load black user lastPhone: %s,pagesize:%s", phoneNumber, pageSize);
            }
            int count = blackUserInfoList.size();
            sum += count;
            String interrupt = redisClient.get(LOAD_DB_INTERRUPT);
            if (StringUtils.equals("1", interrupt)) {
                redisClient.del(LOAD_DB_INTERRUPT);
                done = false;
                break;
            }
            for (BlackUserInfo blackUserInfo : blackUserInfoList) {
                phoneNumber = blackUserInfo.getPhonenumber();
                String key = BLACK_LIST_KEY + blackUserInfo.getPhonenumber();
                LocalDateTime localDateTime = blackUserInfo.getCreatetime();
                String value = DFT.format(localDateTime);
                redisClient.setex(key, value, hangUpSmsConfig.getBlackUserCacheExpire());
            }
            blackUserInfoList = blackUserInfoMapper.selectByAllByPhoneNumber(phoneNumber, pageSize);
        }

        if (done) {
            redisClient.setex(LOAD_DB_FLAG, "done", 3600L);
        }
        return sum;
    }

    public void stopLoading() {
        redisClient.setex(LOAD_DB_INTERRUPT, "1", 60);
    }

    public void batchInsert(List<BlackUserInfo> blackUserList) {
        List<BlackUserInfo> validBlackList = filterExist(blackUserList);
        if (CollectionUtils.isEmpty(validBlackList)) {
            return;
        }
        blackUserInfoMapper.batchInsert(validBlackList);
        List<String> phoneList = validBlackList.stream().map(BlackUserInfo::getPhonenumber).collect(Collectors.toList());
        List<BlackUserInfo> blackList = blackUserInfoMapper.queryBlackUserInfoList(phoneList);
        if (CollectionUtils.isEmpty(blackList)) {
            return;
        }
        for (BlackUserInfo blackUserInfo : blackList) {
            String key = BLACK_LIST_KEY + blackUserInfo.getPhonenumber();
            LocalDateTime localDateTime = blackUserInfo.getCreatetime();
            String value = DFT.format(localDateTime);
            redisClient.setex(key, value, hangUpSmsConfig.getBlackUserCacheExpire());
        }
        redisClient.incrBy(BLACK_LIST_TOTAL, blackList.size());
    }

    public int batchDelete(List<String> blackUserList) {
        if (CollectionUtils.isEmpty(blackUserList)) {
            return 0;
        }
        blackUserInfoMapper.batchDelete(blackUserList);
        for (String phone : blackUserList) {
            String key = BLACK_LIST_KEY + phone;
            redisClient.del(key);
        }
        int size = blackUserList.size() * (-1);
        redisClient.incrBy(BLACK_LIST_TOTAL, size);
        return size;
    }

    public List<BlackUserInfo> queryBlackUserInfoList(List<String> blackUserList) {
        List<BlackUserInfo> blackUserInfoList = new ArrayList<>();
        if (CollectionUtils.isEmpty(blackUserList)) {
            return blackUserInfoList;
        }
        for (String phone : blackUserList) {
            String content = redisClient.get(BLACK_LIST_KEY + phone);
            if (content != null) {
                LocalDateTime localDateTime = LocalDateTime.parse(content, DFT);
                BlackUserInfo blackUserInfo = new BlackUserInfo();
                blackUserInfo.setCreatetime(localDateTime);
                blackUserInfo.setPhonenumber(phone);
                blackUserInfoList.add(blackUserInfo);
            }
        }
        return blackUserInfoList;
    }

    public boolean isBlack(String phone) {
        return CollectionUtils.isNotEmpty(queryBlackUserInfoList(Collections.singletonList(phone)));
    }

    public BlackUserInfo queryBlackUserInfo(String phone) {
        List<BlackUserInfo> blackUserInfoList = queryBlackUserInfoList(Collections.singletonList(phone));
        if (CollectionUtils.isNotEmpty(blackUserInfoList)) {
            return blackUserInfoList.get(0);
        }
        return null;
    }

    public long getCount() {
        String content = redisClient.get(BLACK_LIST_TOTAL);
        if (StringUtils.isNotEmpty(content)) {
            try {
                return Long.parseLong(content);
            } catch (NumberFormatException e) {
                LogUtils.error(e, log, "get total error.");
            }
        }
        return 0;
    }

    private List<BlackUserInfo> filterExist(List<BlackUserInfo> blackUserList) {
        if (CollectionUtils.isEmpty(blackUserList)) {
            return blackUserList;
        }
        List<BlackUserInfo> validBlackList = new ArrayList<>();
        List<String> phoneList = blackUserList.stream().map(BlackUserInfo::getPhonenumber).collect(Collectors.toList());
        Map<String, BlackUserInfo> blackUserInfoMap = getExistBlackPhone(phoneList);

        for (BlackUserInfo blackUsr : blackUserList) {
            if (blackUserInfoMap.get(blackUsr.getPhonenumber()) == null) {
                // 将不存在的手机号返回
                validBlackList.add(blackUsr);
            }
        }
        return validBlackList;
    }

    private Map<String, BlackUserInfo> getExistBlackPhone(List<String> phoneList) {
        Map<String, BlackUserInfo> blackUserInfoMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(phoneList)) {
            List<BlackUserInfo> blackList = blackUserInfoMapper.queryBlackUserInfoList(phoneList);
            for (BlackUserInfo blackUserInfo : blackList) {
                blackUserInfoMap.put(blackUserInfo.getPhonenumber(), blackUserInfo);
            }
        }
        return blackUserInfoMap;
    }
}
